package com.doge.calcalbum.business.update

import com.doge.calcalbum.utils.appContext
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
import java.security.cert.X509Certificate
import java.util.concurrent.Executors
import javax.net.ssl.*

object DownloadUtil {

    private val tempDir by lazy { appContext.externalCacheDir!! }
    private val downloadThreadPool = Executors.newFixedThreadPool(4)
    private val hostnameVerifier = HostnameVerifier { _, _ -> true }
    private val sslSocketFactory: SSLSocketFactory? = try {
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, arrayOf(object : X509TrustManager {
            override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) = Unit
            override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) = Unit
            override fun getAcceptedIssuers() = null
        }), null)
        sslContext.socketFactory
    } catch (ignore: Throwable) {
        null
    }
    private val downloadInfos = HashMap<Int, HashSet<DownloadInfo>>()

    fun download(url: String, saveFile: File, callback: (status: Status, downLoadSize: Int, contentLength: Int) -> Unit): Int {
        val taskId = url.hashCode()
        if (downloadInfos.containsKey(taskId)) {
            addDownloadInfo(taskId, url, saveFile, callback)
            return taskId
        }
        addDownloadInfo(taskId, url, saveFile, callback)
        innerDownload(taskId, url)
        return taskId
    }

    private fun addDownloadInfo(taskId: Int, url: String, saveFile: File, callback: (status: Status, downLoadSize: Int, contentLength: Int) -> Unit) {
        val callbacks = downloadInfos[taskId] ?: HashSet()
        callbacks.add(DownloadInfo(url, saveFile, callback))
        downloadInfos[taskId] = callbacks
    }


    fun cancel(taskId: Int) {
        downloadInfos.remove(taskId)
    }


    private fun innerDownload(taskId: Int, url: String) {
        downloadThreadPool.execute {
            android.os.Process.setThreadPriority(android.os.Process.myTid(), android.os.Process.THREAD_PRIORITY_BACKGROUND)
            mkdirs(tempDir)
            val tempFile = File(tempDir, "__temp_${url.hashCode()}")
            var retryCount = 0
            var sum = 0
            var contentLength = 0
            while (retryCount < 10) {
                try {
                    val conn = URL(url).openConnection() as HttpURLConnection
                    if (conn is HttpsURLConnection) {
                        conn.hostnameVerifier = hostnameVerifier
                        if (sslSocketFactory != null)
                            conn.sslSocketFactory = sslSocketFactory
                    }
                    conn.requestMethod = "GET"
                    conn.connectTimeout = 5000
                    conn.readTimeout = 5000
                    if (tempFile.exists()) {
                        sum = tempFile.length().toInt()
                        conn.setRequestProperty("Range", "bytes=$sum-")
                    }
                    val hasDownload = when (conn.responseCode) {
                        206 -> {
                            contentLength = conn.contentLength + sum
                            true
                        }
                        200 -> {
                            contentLength = conn.contentLength
                            sum = 0
                            true
                        }
                        else -> false
                    }
                    if (hasDownload) {
                        val ins = conn.inputStream
                        val fos = FileOutputStream(tempFile, true)
                        val buffer = ByteArray(81960)
                        var readLength: Int
                        while (true) {
                            readLength = ins.read(buffer)
                            if (readLength == -1)
                                break
                            fos.write(buffer, 0, readLength)
                            sum += readLength
                            synchronized(this) {
                                val set = downloadInfos[taskId]
                                if (set.isNullOrEmpty()) { //中途取消了。。。
                                    retryCount = 100
                                    fos.close()
                                    ins.close()
                                    conn.disconnect()
                                    tempFile.delete()
                                    throw RuntimeException("cancel")
                                } else
                                    set.forEach { it.callback.invoke(Status.DOWNLOADING, sum, contentLength) }
                            }
                        }
                        fos.flush()
                        fos.close()
                        ins.close()
                        conn.disconnect()
                        downloadInfos.remove(taskId)?.forEach {
                            if (it.saveFile.exists())
                                it.saveFile.delete()
                            if (!mkdirs(it.saveFile.parentFile!!))
                                it.callback.invoke(Status.DOWNLOAD_FAILED, sum, contentLength)
                            else
                                try {
                                    tempFile.copyTo(it.saveFile, true)
                                    it.callback.invoke(Status.DOWNLOAD_SUCCESS, sum, contentLength)
                                } catch (ignore: Throwable) {
                                    it.callback.invoke(Status.DOWNLOAD_FAILED, sum, contentLength)
                                }
                        }
                        retryCount = 100
                    } else
                        retryCount++
                } catch (ignore: Throwable) {
                    retryCount++
                }
            }
            val status = when (retryCount) {
                101 -> Status.DOWNLOAD_FAILED
                else -> Status.DOWNLOAD_CANCEL
            }
            synchronized(this) { downloadInfos.remove(taskId)?.forEach { it.callback.invoke(status, sum, contentLength) } }
            tempFile.delete()
        }
    }

    private fun mkdirs(dir: File): Boolean {
        if (!dir.exists() || !dir.isDirectory) {
            dir.delete()
            return dir.mkdirs()
        }
        return true
    }

    enum class Status {
        DOWNLOADING,
        DOWNLOAD_SUCCESS,
        DOWNLOAD_FAILED,
        DOWNLOAD_CANCEL
    }

    private class DownloadInfo(val url: String, val saveFile: File, val callback: (Status, Int, Int) -> Unit)


}